package de.hs.rm.UJAdapter.factory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
import javax.management.openmbean.OpenMBeanOperationInfoSupport;
import javax.management.openmbean.OpenMBeanParameterInfoSupport;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;

import de.hs.rm.UJAdapter.impl.UJAction;
import de.hs.rm.UJAdapter.impl.UJActionGetter;
import de.hs.rm.UJAdapter.impl.UJActionOpenType;
import de.hs.rm.UJAdapter.impl.UJActionOpenTypeGetter;
import de.hs.rm.UJAdapter.impl.UJActionOpenTypeSetter;
import de.hs.rm.UJAdapter.impl.UJActionSetter;
import de.hs.rm.UJAdapter.impl.UJStateVariable;
import de.hs.rm.UJAdapter.util.ClassTypeUtil;

public class UJActionFactory {
	
	private static final String ARRAY_PREFIX = "_";
	
	public static UJAction createSetter(
			MBeanServerConnection mBeanServerConnection,
			UJStateVariable sV,
			ObjectName objName)
	{
		UJActionSetter actionSetter = new UJActionSetter();
		actionSetter.setmBeanServerConnection(mBeanServerConnection);
		actionSetter.setName("set" + sV.getName());
		actionSetter.setStateVariable(sV);
    	String[] inParamNames = {sV.getName()};
    	actionSetter.setInputArgumentsNames(inParamNames);
    	actionSetter.setObjName(objName);
    	return actionSetter;
	}
	
	public static UJAction createGetter(
			MBeanServerConnection mBeanServerConnection,
			UJStateVariable sV,
			ObjectName objName)
	{
		UJActionGetter actionGetter = new UJActionGetter();
		actionGetter.setmBeanServerConnection(mBeanServerConnection);
		if(sV.isIs())
			actionGetter.setName("is" + sV.getName());
		else
			actionGetter.setName("get" + sV.getName());
		actionGetter.setStateVariable(sV);
    	String[] outParamNames = {sV.getName()};
    	
    	actionGetter.setOutputArgumentsNames(outParamNames);
    	actionGetter.setReturnArgumentName(sV.getName());		    	
    	actionGetter.setObjName(objName);
    	return actionGetter;
	}
	
	public static UJAction createOperation(
			MBeanServerConnection mBeanServerConnection,
			MBeanOperationInfo mbeanOperation,
			ArrayList<UJStateVariable> genericStateVariables,
			ObjectName objName,
			String upnpName)
	{
		UJAction action = new UJAction();
    	action.setmBeanServerConnection(mBeanServerConnection);
    	action.setName(upnpName);
    	action.setJavaName(mbeanOperation.getName());
		//action.setStateVariable(stateVariables[mbeanAttributes.length]);//GENERIC
    	
		MBeanParameterInfo[] mbeanParams = mbeanOperation.getSignature();
		String[] inParamNames = new String[mbeanParams.length];
		String[] inParamUPnPTypes = new String[mbeanParams.length];
		String[] inParamJavaTypes = new String[mbeanParams.length];
		boolean gsvExists = false;
		// input arguments
		for(int j = 0; j < mbeanParams.length; j++)
		{
			gsvExists = false;
			inParamNames[j] = mbeanParams[j].getName();
			inParamUPnPTypes[j] = ClassTypeUtil.getUPNPDataTypeFromString(mbeanParams[j].getType());
			inParamJavaTypes[j] = mbeanParams[j].getType();
			for (UJStateVariable gsv : genericStateVariables)
			{
				if (gsv.getUPnPDataType() == inParamUPnPTypes[j])
				{
					gsvExists = true;
					action.addStateVariable(inParamNames[j], gsv);
				}
			}
			if(!gsvExists)
			{
				UJStateVariable stateVariable =
						UJStateVariableFactory.createActionStateVariable(inParamUPnPTypes[j], mbeanParams[j].getType());
				genericStateVariables.add(stateVariable);
		       	action.addStateVariable(inParamNames[j], stateVariable);
			}
		}
		
		
		//output arguments
		if (ClassTypeUtil.getJavaDataTypeFromString(mbeanOperation.getReturnType()) != null)
		{
    		String[] outParamNames = {"returnValue"};
    		String[] outParamUPnPTypes = {ClassTypeUtil.getUPNPDataTypeFromString(mbeanOperation.getReturnType())};
    		
    		gsvExists = false;
    		for (UJStateVariable gsv : genericStateVariables)
			{
				if (gsv.getUPnPDataType() == outParamUPnPTypes[0])
				{
					gsvExists = true;
					action.addStateVariable(outParamNames[0], gsv);
				}
			}
			if(!gsvExists)
			{
				UJStateVariable stateVariable = 
						UJStateVariableFactory.createActionStateVariable(outParamUPnPTypes[0], mbeanOperation.getReturnType());
		       	genericStateVariables.add(stateVariable);
		       	action.addStateVariable(outParamNames[0], stateVariable);
			}
			
			//set return type
			if(outParamNames.length > 0)
				action.setOutputArgumentsNames(outParamNames);
		}
    		
		//set arguments for action
		action.setInputArgumentsNames(inParamNames);
		if(inParamJavaTypes.length > 0)
			action.setInputArgumentsTypes(inParamJavaTypes);
    	action.setObjName(objName);
    	return action;
	}
	
	public static UJAction createOpenTypeSetter(
			MBeanServerConnection mBeanServerConnection,
			OpenMBeanAttributeInfoSupport openMBeanAttr,
			ArrayList<UJStateVariable> genericOpenStateVariablesList,
			ObjectName objName)
	{
		OpenType openType = openMBeanAttr.getOpenType();
		
		UJActionOpenTypeSetter action = new UJActionOpenTypeSetter();
    	action.setmBeanServerConnection(mBeanServerConnection);
    	action.setObjName(objName);
    	action.setName("set" + openMBeanAttr.getName());
    	action.setAttributeOpenType(openType);
    	action.setAttributeName(openMBeanAttr.getName());
    	
    	//create primitive types list from complex type 
    	ArrayList<String> openTypeAttributeTypeList
			= new ArrayList<String>();
    	
    	//create names list from complex type 
    	ArrayList<String> openTypeAttributeNameList
			= new ArrayList<String>();
    	
		createOpenTypeAttributeTypeList(openType, openTypeAttributeTypeList);
		createOpenTypeAttributeNameList(openType, openTypeAttributeNameList);		
		
		String openTypeClassString = openType.toString();
		int arraySize = 0;
		if(openMBeanAttr.isReadable())
		{
			if(ClassTypeUtil.getOpenType(openTypeClassString) ==
					ClassTypeUtil.OPENTYPE_ARRAY)
			{
				arraySize = getArraySize(mBeanServerConnection, objName, action.getAttributeName());
			}
			
			if(ClassTypeUtil.getOpenType(openTypeClassString) ==
					ClassTypeUtil.OPENTYPE_TABULAR)
			{
				arraySize = getTabularSize(mBeanServerConnection, objName, action.getAttributeName());
			}
		}
		
		action.setArraySize(arraySize);		
		
		String inParamNames[] = null;
		String inParamUPnPTypes[] = null;
		
		int aLength = openTypeAttributeTypeList.size();
		
		if(arraySize == 0)
		{
			inParamNames = new String[openTypeAttributeTypeList.size()];
			inParamUPnPTypes = new String[openTypeAttributeTypeList.size()];
		
			//add State Variable for every Attribute 
			for (int i = 0; i < openTypeAttributeTypeList.size(); i++)
			{
				inParamUPnPTypes[i] =
						ClassTypeUtil.getUPnPDataTypefromSimpleType(openTypeAttributeTypeList.get(i));
				inParamNames[i] = openTypeAttributeNameList.get(i);
				for (UJStateVariable oTsV : genericOpenStateVariablesList)
				{
					if (oTsV.getUPnPDataType() == inParamUPnPTypes[i])
					{
						action.addStateVariable(inParamNames[i], oTsV);
					}				
				}
			}
		}
		else
		{
			inParamNames = new String[aLength * arraySize];
			inParamUPnPTypes = new String[aLength * arraySize];
			
			for(int i = 0; i < arraySize; i++)
			{
				for (int j = 0; j < aLength; j++)
				{
					inParamNames[(i*aLength)+j] = i + ARRAY_PREFIX + openTypeAttributeNameList.get(j);
					inParamUPnPTypes[(i*aLength)+j] = ClassTypeUtil.getUPnPDataTypefromSimpleType(openTypeAttributeTypeList.get(j));
					
					for (UJStateVariable oTsV : genericOpenStateVariablesList)
					{
						if (oTsV.getUPnPDataType() == inParamUPnPTypes[(i*aLength)+j])
						{
							action.addStateVariable(inParamNames[(i*aLength)+j], oTsV);
						}				
					}					
				}
			}		
		
		}
		action.setInputArgumentsNames(inParamNames);
		action.setInputArgumentsTypes(inParamUPnPTypes);
    	
    	return action;
	}
	
	public static UJActionOpenTypeGetter createOpenTypeGetter(
			MBeanServerConnection mBeanServerConnection,
			OpenMBeanAttributeInfoSupport openMBeanAttr,
			ArrayList<UJStateVariable> genericOpenStateVariablesList,
			ObjectName objName)
	{
		OpenType openType = openMBeanAttr.getOpenType();
		
		UJActionOpenTypeGetter action = new UJActionOpenTypeGetter();
    	action.setmBeanServerConnection(mBeanServerConnection);
    	action.setObjName(objName);
    	action.setName("get" + openMBeanAttr.getName());
    	action.setAttributeOpenType(openType);
    	action.setAttributeName(openMBeanAttr.getName());
    	
    	//create primitive types list from complex type 
    	ArrayList<String> openTypeAttributeTypeList
			= new ArrayList<String>();
    	
    	//create names list from complex type 
    	ArrayList<String> openTypeAttributeNameList
			= new ArrayList<String>();
    	
		createOpenTypeAttributeTypeList(openType, openTypeAttributeTypeList);
		createOpenTypeAttributeNameList(openType, openTypeAttributeNameList);		
		
		
		String openTypeClassString = openType.toString();
		int arraySize = 0;
		if(ClassTypeUtil.getOpenType(openTypeClassString) ==
				ClassTypeUtil.OPENTYPE_ARRAY)
		{
			arraySize = getArraySize(mBeanServerConnection, objName, action.getAttributeName());
		}
		
		if(ClassTypeUtil.getOpenType(openTypeClassString) ==
				ClassTypeUtil.OPENTYPE_TABULAR)
		{
			arraySize = getTabularSize(mBeanServerConnection, objName, action.getAttributeName());
		}		
		
		String outParamNames[] = null;
		String outParamUPnPTypes[] = null;
		int aLength = openTypeAttributeTypeList.size();
		
		if(arraySize == 0)
		{
			outParamNames = new String[aLength];
			outParamUPnPTypes = new String[aLength];
			
			//add State Variable for every Attribute 
			for (int i = 0; i < aLength; i++)
			{
				outParamUPnPTypes[i] =
						ClassTypeUtil.getUPnPDataTypefromSimpleType(openTypeAttributeTypeList.get(i));
				outParamNames[i] = openTypeAttributeNameList.get(i);
				for (UJStateVariable oTsV : genericOpenStateVariablesList)
				{
					if (oTsV.getUPnPDataType() == outParamUPnPTypes[i])
					{
						action.addStateVariable(outParamNames[i], oTsV);
					}				
				}
			}
		}
		else
		{
			outParamNames = new String[aLength * arraySize];
			outParamUPnPTypes = new String[aLength * arraySize];
			
			for(int i = 0; i < arraySize; i++)
			{
				for (int j = 0; j < aLength; j++)
				{
					outParamNames[(i*aLength)+j] = i + ARRAY_PREFIX + openTypeAttributeNameList.get(j);
					outParamUPnPTypes[(i*aLength)+j] = ClassTypeUtil.getUPnPDataTypefromSimpleType(openTypeAttributeTypeList.get(j));
					
					for (UJStateVariable oTsV : genericOpenStateVariablesList)
					{
						if (oTsV.getUPnPDataType() == outParamUPnPTypes[(i*aLength)+j])
						{
							action.addStateVariable(outParamNames[(i*aLength)+j], oTsV);
						}				
					}					
				}
			}		
		}		
		action.setOutputArgumentsNames(outParamNames);
		action.setOutputArgumentsTypes(outParamUPnPTypes);
    	
    	return action;
	}
	
	public static UJActionOpenType createOperationOpenType(
			MBeanServerConnection mBeanServerConnection,
			MBeanOperationInfo mbeanOperation,
			ArrayList<UJStateVariable> genericOpenStateVariablesList,
			ObjectName objName,
			String upnpName)
	{
		UJActionOpenType action = new UJActionOpenType();
		action.setmBeanServerConnection(mBeanServerConnection);
    	action.setObjName(objName);
    	action.setName(upnpName);
    	action.setJavaName(mbeanOperation.getName());
    	MBeanParameterInfo[] mbeanParams = mbeanOperation.getSignature();
    	
    	ArrayList<String> inParamTypeList
			= new ArrayList<String>();
    	ArrayList<String> inParamJavaTypeList
		= new ArrayList<String>();
     	ArrayList<String> inParamNameList
			= new ArrayList<String>();
    	
    	for(int i = 0; i < mbeanParams.length; i++)
    	{
    		if(mbeanParams[i].getClass().toString().contains("OpenMBeanParameterInfoSupport"))
	    	{
    			OpenMBeanParameterInfoSupport openmbeanParam = (OpenMBeanParameterInfoSupport)mbeanParams[i];
    			OpenType openType = openmbeanParam.getOpenType();
    			String openTypeClassString = openType.toString();
//    			createOpenTypeAttributeTypeList(openType, inParamTypeList);
//    			createOpenTypeAttributeNameList(openType, inParamNameList);
    			//disable if input parameter are not simle types
    			if(ClassTypeUtil.getOpenType(openTypeClassString) != ClassTypeUtil.OPENTYPE_SIMPLE)
    			{
    				action.setActivated(false);
    				inParamTypeList.add(ClassTypeUtil.getUPnPDataTypefromSimpleType(openType.getTypeName()));
    			}
    			else
    			{
    				SimpleType simpleType = (SimpleType) openType;
    				inParamTypeList.add(simpleType.getTypeName());
    			}
    			inParamNameList.add(mbeanParams[i].getName());
    		}
    		else
    		{
    			inParamNameList.add(mbeanParams[i].getName());
    			inParamTypeList.add(ClassTypeUtil.getUPNPDataTypeFromString(mbeanParams[i].getType()));
    		}
    		inParamJavaTypeList.add(mbeanParams[i].getType());
    	}
    	
    	String inParamNames[] = null;
		String inParamUPnPTypes[] = null;
		String inParamJavaTypes[] = null;
		
		int inLength = inParamTypeList.size();
		
		inParamNames = new String[inLength];
		inParamUPnPTypes = new String[inLength];
		inParamJavaTypes = new String[inLength]; 
		//add State Variable for every Attribute 
		for (int i = 0; i < inLength; i++)
		{
			inParamJavaTypes[i] = inParamJavaTypeList.get(i);
			inParamUPnPTypes[i] =
					ClassTypeUtil.getUPnPDataTypefromSimpleType(inParamTypeList.get(i));
			inParamNames[i] = inParamNameList.get(i);
			for (UJStateVariable oTsV : genericOpenStateVariablesList)
			{
				if (oTsV.getUPnPDataType() == inParamUPnPTypes[i])
				{
					action.addStateVariable(inParamNames[i], oTsV);
				}				
			}
		}
		
		action.setInputArgumentsNames(inParamNames);
		action.setInputArgumentsTypes(inParamJavaTypes);
		
		
		ArrayList<String> outParamTypeList
			= new ArrayList<String>();
		ArrayList<String> outParamNameList
			= new ArrayList<String>();
		
		
		String returnType = mbeanOperation.getReturnType();
			
		if(mbeanOperation.getClass().toString().contains("OpenMBeanOperationInfoSupport"))
		{
			OpenMBeanOperationInfoSupport openMbeanOperation = (OpenMBeanOperationInfoSupport)mbeanOperation;
			OpenType returnOpenType = openMbeanOperation.getReturnOpenType();
			String openTypeClassString = returnOpenType.toString();
			int openTypeClass = ClassTypeUtil.getOpenType(openTypeClassString);
			action.setReturnOpenType(true);
			//disable if return type is not simple/composite type
			if((openTypeClass != ClassTypeUtil.OPENTYPE_SIMPLE) &&
				(openTypeClass != ClassTypeUtil.OPENDATA_COMPOSITE))
			{
				action.setActivated(false);								
			}
			
			if(openTypeClass == ClassTypeUtil.OPENDATA_COMPOSITE)
			{			
				createOpenTypeAttributeTypeList(returnOpenType, outParamTypeList);
				createOpenTypeAttributeNameList(returnOpenType, outParamNameList);
			}
			else
			{
				outParamTypeList.add(ClassTypeUtil.getUPnPDataTypefromSimpleType(returnOpenType.toString()));
				outParamNameList.add(returnOpenType.getTypeName());
				
			}
			
			String outParamNames[] = null;
			String outParamUPnPTypes[] = null;
		
			int outLength = outParamTypeList.size();
			
			if(outLength > 0)
			{
				outParamNames = new String[outLength];
				outParamUPnPTypes = new String[outLength];
				
				//add State Variable for every Attribute 
				for (int i = 0; i < outLength; i++)
				{
					outParamUPnPTypes[i] =
							ClassTypeUtil.getUPnPDataTypefromSimpleType(outParamTypeList.get(i));
					outParamNames[i] = outParamNameList.get(i);
					for (UJStateVariable oTsV : genericOpenStateVariablesList)
					{
						if (oTsV.getUPnPDataType() == outParamUPnPTypes[i])
						{
							action.addStateVariable(outParamNames[i], oTsV);
						}				
					}
				}
			}
			
			action.setOutputArgumentsNames(outParamNames);
			action.setOutputArgumentsTypes(outParamUPnPTypes);
		}
		else if(!returnType.contains("void"))
		{
			String[] outParamNames = {"returnValue"};
    		String[] outParamUPnPTypes = {ClassTypeUtil.getUPNPDataTypeFromString(mbeanOperation.getReturnType())};
    		action.setOutputArgumentsNames(outParamNames);
			action.setOutputArgumentsTypes(outParamUPnPTypes);
		}		
		return action;
	}
	
	private static int getArraySize(
			MBeanServerConnection mBeanServerConnection, ObjectName objName,
			String attributeName)
	{
		int arraySize = 0;
	
		try {
			Object result = mBeanServerConnection.getAttribute(objName
				, attributeName);
			Object[] arrayData = (Object[]) result;
			arraySize = arrayData.length;
		} catch (AttributeNotFoundException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (InstanceNotFoundException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (MBeanException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (ReflectionException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		}
		catch(Exception e)
		{
		
		}
		finally
		{
			return arraySize;
		}
		
	}
	
	private static int getTabularSize(
			MBeanServerConnection mBeanServerConnection, ObjectName objName,
			String attributeName)
	{
		int arraySize = 0;
	
		try {
			Object result = mBeanServerConnection.getAttribute(objName
				, attributeName);
			TabularDataSupport tOpenData = 
					(TabularDataSupport) result;
			arraySize = tOpenData.size();
		} catch (AttributeNotFoundException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (InstanceNotFoundException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (MBeanException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (ReflectionException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		}
		catch(Exception e)
		{
		
		}
		finally
		{
			return arraySize;
		}
	
	}
	
	public static void createOpenTypeAttributeTypeList(OpenType openType, ArrayList<String> openTypeAttributeTypeList)
	{
		String openTypeClassString = openType.toString();		
		//recursively add simple type state variables from complex types
		switch(ClassTypeUtil.getOpenType(openTypeClassString))
		{
			case ClassTypeUtil.OPENTYPE_COMPOSITE:
				CompositeType compType= (CompositeType) openType;
				Set<String> keys = compType.keySet();
    			Iterator<String> it = keys.iterator();    			
    			while(it.hasNext())
    			{
    				String key = it.next();
    				createOpenTypeAttributeTypeList(compType.getType(key), openTypeAttributeTypeList);
    			}	
			break;
			
			case ClassTypeUtil.OPENTYPE_ARRAY:
				ArrayType arrayType = (ArrayType) openType;
				//for(int i = 0; i < arrayType.getDimension(); i++)
				createOpenTypeAttributeTypeList(arrayType.getElementOpenType(), openTypeAttributeTypeList);
			break;
				
			case ClassTypeUtil.OPENTYPE_TABULAR:
				TabularType tabType = (TabularType) openType;
				createOpenTypeAttributeTypeList(tabType.getRowType(), openTypeAttributeTypeList);
			break;
			
			case ClassTypeUtil.OPENTYPE_SIMPLE:
				SimpleType simpleType = (SimpleType) openType;
				openTypeAttributeTypeList.add(simpleType.getTypeName());
			break;
		}		
	}
	
	public static void createOpenTypeAttributeNameList(OpenType openType, ArrayList<String> openTypeAttributeNameList)
	{
		String openTypeClassString = openType.toString();		
		//recursively add simple type state variables from complex types
		switch(ClassTypeUtil.getOpenType(openTypeClassString))
		{
			case ClassTypeUtil.OPENTYPE_COMPOSITE:
				CompositeType compType= (CompositeType) openType;
				String compTypeName = compType.getTypeName();
				if(compTypeName.contains("."))
					compTypeName = compTypeName.substring(compTypeName.lastIndexOf(".")+1);
				Set<String> keys = compType.keySet();
    			Iterator<String> it = keys.iterator();    			
    			while(it.hasNext())
    			{
    				String key = it.next();
    				String tmpOpenTypeCS = compType.getType(key).toString();
    				if(ClassTypeUtil.getOpenType(tmpOpenTypeCS) == ClassTypeUtil.OPENTYPE_SIMPLE)
    					openTypeAttributeNameList.add(compTypeName + "." +key);	
    				else
    					createOpenTypeAttributeNameList(compType.getType(key), openTypeAttributeNameList);
    			}	
			break;
			
			case ClassTypeUtil.OPENTYPE_ARRAY:
				ArrayType arrayType = (ArrayType) openType;
				//for(int i = 0; i < arrayType.getDimension(); i++)
				createOpenTypeAttributeNameList(arrayType.getElementOpenType(), openTypeAttributeNameList);
			break;
				
			case ClassTypeUtil.OPENTYPE_TABULAR:
				TabularType tabType = (TabularType) openType;
				createOpenTypeAttributeNameList(tabType.getRowType(), openTypeAttributeNameList);
			break;
			
			case ClassTypeUtil.OPENTYPE_SIMPLE:
				SimpleType simpleType = (SimpleType) openType;
				openTypeAttributeNameList.add(simpleType.getTypeName());
			break;
		}		
	}

}
